iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 7

Day 07|Props 傳到懷疑人生時,Context 到底是在解決什麼?

  • 分享至 

  • xImage
  •  

上一篇講到 Component 拆分之後,很快會遇到另一個問題。

假設現在有:

Page
↓
Section
↓
Panel
↓
Button

而真正需要某份資料的,其實只有最下面的:

Button

但資料一開始存在最上面的:

Page

那最直覺的做法就是一路傳 Props:

Page
↓ props
Section
↓ props
Panel
↓ props
Button

看起來好像沒問題。

但如果中間的:

Section
Panel

根本不需要這份資料,只是負責「幫忙往下傳」,

程式就開始變得有點奇怪。


先看最直覺的 Props 傳法

假設最上層有:

const user = {
  name: "小潔",
};

然後:

function Page() {
  const user = {
    name: "小潔",
  };

  return <Section user={user} />;
}

Section 再傳:

function Section({ user }) {
  return <Panel user={user} />;
}

Panel 再傳:

function Panel({ user }) {
  return <UserButton user={user} />;
}

最後:

function UserButton({ user }) {
  return (
    <button>
      {user.name}
    </button>
  );
}

資料流:

Page
│
│ user
↓
Section
│
│ user
↓
Panel
│
│ user
↓
UserButton

問題是:

Section
Panel

其實根本沒有使用 user

它們只是:

收到 → 再傳下去。


這就是常聽到的 Props Drilling

這種一層一層把 Props 往下傳的情況,常被稱為:

Props Drilling

不是說:

Props 傳很多層一定錯。

而是當中間很多 Component 根本不需要這份資料時,維護成本就可能開始增加。

例如本來:

<Section user={user} />

後來又增加:

<Section
  user={user}
  permission={permission}
  currency={currency}
  timezone={timezone}
/>

接著 Section 自己沒用,又全部傳:

<Panel
  user={user}
  permission={permission}
  currency={currency}
  timezone={timezone}
/>

再下一層又繼續傳。

最後可能變成:

Page
↓
4 個 Props

Section
↓
4 個 Props

Panel
↓
4 個 Props

真正需要資料的 Child

這時候我以前就會開始疑惑:

為什麼中間這麼多 Component 都要知道這些東西?


Context 可以先想成「共用佈告欄」

我自己後來比較容易理解 Context 的方式,是把它想成一個佈告欄。

以前:

Page
↓
把資料交給 Section

Section
↓
再交給 Panel

Panel
↓
再交給 Button

很像一張紙一直往下傳。

Context 比較像:

Parent
↓
把共享資料放到一個範圍裡

        ┌───────────────┐
        │   Context     │
        │               │
        │ user          │
        │ permission    │
        └───────────────┘
             ↑
     範圍內需要的人
       自己來讀

所以中間的 Component:

Section
Panel

如果不需要 user,就不用只是為了傳遞而收到它。


Context 通常有三個角色

先看一個簡單版本:

import {
  createContext,
  useContext,
} from "react";

const UserContext = createContext(null);

這裡:

UserContext

可以先理解成:

建立一個放共享資料的位置。


接著 Provider:

function Page() {
  const user = {
    name: "小潔",
  };

  return (
    <UserContext.Provider value={user}>
      <Section />
    </UserContext.Provider>
  );
}

這裡的:

value={user}

就是把資料提供給 Provider 範圍裡面的 Component。

流程可以想成:

Page
↓
UserContext.Provider
↓
提供 user
↓
底下的 Component 可以讀

真正需要資料的地方:

function UserButton() {
  const user = useContext(UserContext);

  return (
    <button>
      {user.name}
    </button>
  );
}

這裡:

useContext(UserContext)

可以先理解成:

我要去這個 Context 裡拿目前共享的資料。

所以三個角色可以先記成:

createContext
→ 建立 Context

Provider
→ 把資料提供出去

useContext
→ 把資料拿進來使用

加入 Context 後,資料流變成什麼?

原本:

Page
↓ user
Section
↓ user
Panel
↓ user
UserButton

改成 Context:

Page
│
│ user
↓
UserContext.Provider
│
├─ Section
│   └─ Panel
│       └─ UserButton
│            ↑
│       useContext()
│
└────────────┘

中間:

Section
Panel

不需要 user

就不必為了「幫忙傳資料」而接 Props。

這就是 Context 最容易理解的價值之一。


但 Context 不是用來消滅所有 Props

這裡是我覺得很重要的一點。

學到 Context 後,很容易開始覺得:

那我是不是什麼都放 Context 就好了?

其實不是。

如果只有:

Parent
↓
Child

而且 Child 本來就需要那份資料:

<ProductInfo products={products} />

這樣 Props 本身就非常清楚。

沒有必要為了少寫一個 Props,就建立 Context。

例如:

DetailPage
↓
ProductInfo

只差一層,而且:

ProductInfo

本來就需要 products

那:

<ProductInfo products={products} />

反而很好讀。


什麼時候我才會開始想到 Context?

我目前比較會在這些狀況下考慮:

同一份資料
↓
很多不同 Component 都需要

或

資料需要經過很多層
↓
中間 Component 根本不用
↓
只是一直幫忙傳 Props

例如:

DetailPage
│
├─ Header
│
├─ BasicInfo
│
├─ ProductInfo
│
├─ DocumentInfo
│
└─ ActionArea

如果很多地方都需要:

detail
permission
currentMode

這時候就可以開始思考:

這些是不是適合放到一個共享的 Context?

而不是一直變成:

<BasicInfo
  detail={detail}
  permission={permission}
  currentMode={currentMode}
/>

<ProductInfo
  detail={detail}
  permission={permission}
  currentMode={currentMode}
/>

<DocumentInfo
  detail={detail}
  permission={permission}
  currentMode={currentMode}
/>

實際拆頁面時,我才真的開始理解 Context

我以前看到:

useContext(...)

第一個反應通常是:

又來一個 Hook。

但後來真的碰到比較大的頁面,拆成很多 Component 之後,才發現問題不是:

「我要不要學 Context?」

而是:

這份資料很多地方都要
↓
如果一直用 Props 傳
↓
中間很多 Component 只是轉手
↓
資料依賴開始變得很亂

這時候 Context 才開始有實際意義。

所以對我來說,學 Context 最重要的不是先背:

createContext
Provider
useContext

而是先知道:

它是為了解決什麼資料傳遞問題而存在。


但 Context 也有代價

Context 雖然可以減少 Props Drilling,但它也不是免費的。

如果什麼東西都塞進 Context:

user
permission
form
loading
modal
table
filters
detail
button
...

最後可能變成另一種問題:

Component 表面上沒有 Props,但其實背後偷偷依賴很多 Context 資料。

這時候反而更難看出:

這個 Component 到底需要什麼?

所以我現在比較喜歡把它想成:

Props
→ 明確、直接的資料依賴

Context
→ 某個範圍內需要共享的資料

不是:

Props 很麻煩
↓
全部改 Context

今天先記住這張圖

如果資料只是:

Parent
↓
Child

而且 Child 本來就需要
→ Props 很適合


如果變成:

Parent
↓
Middle A
↓
Middle B
↓
Middle C
↓
真正需要資料的 Child

而中間都只是轉手
→ 可以開始思考 Context

所以看到 Context 時,我現在會先問:

這份資料為什麼要共享?
↓
有哪些 Component 需要?
↓
如果不用 Context,
是不是會產生很多沒有意義的 Props 傳遞?

而不是看到:

useContext()

就先害怕它。

Context 本質上還是在處理我們這幾天一直追的同一件事:

資料從哪裡來?誰需要它?又該怎麼到那裡?

下一篇,我想繼續接一個 React 裡非常容易搞混的東西:

useEffect 到底什麼時候會執行?為什麼有時候 API 一進頁面就自己打出去了?


上一篇
Day 06|Component 不是拆越多越好:一個頁面到底什麼時候該拆?
下一篇
Day 08|useEffect 到底什麼時候執行?為什麼一進頁面 API 就自己打了?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言